#ifndef U2SharedPtr_H
#define U2SharedPtr_H

#include "U2PreRequest.h"
#include "U2MemoryAllocatorConfig.h"


U2EG_NAMESPACE_BEGIN


/// The method to use to free memory on destruction
enum SharedPtrFreeMethod
{
	/// Use U2_DELETE to free the memory
	SPFM_DELETE,
	/// Use U2_DELETE_T to free (only MEMCATEGORY_GENERAL supported)
	SPFM_DELETE_T,
	/// Use U2_FREE to free (only MEMCATEGORY_GENERAL supported)
	SPFM_FREE
};

/** Reference-counted shared pointer, used for objects where implicit destruction is 
    required. 
@remarks
    This is a standard shared pointer implementation which uses a reference 
    count to work out when to delete the object. 
@par
	If U2_THREAD_SUPPORT is defined to be 1, use of this class is thread-safe.
*/
template<class T> class U2SharedPtr
{
protected:
	T* pRep;
	unsigned int* pUseCount;
	SharedPtrFreeMethod useFreeMethod; // if we should use U2_FREE instead of U2_DELETE

public:
	U2_AUTO_SHARED_MUTEX // public to allow external locking
	/** Constructor, does not initialise the U2SharedPtr.
		@remarks
			<b>Dangerous!</b> You have to call bind() before using the U2SharedPtr.
	*/
	U2SharedPtr() : pRep(0), pUseCount(0), useFreeMethod(SPFM_DELETE)
    {
        U2_SET_AUTO_SHARED_MUTEX_NULL
    }

	/** Constructor.
	@param rep The pointer to take ownership of
	@param freeMode The mechanism to use to free the pointer
	*/
    template< class Y>
	explicit U2SharedPtr(Y* rep, SharedPtrFreeMethod inFreeMethod = SPFM_DELETE) 
		: pRep(rep)
		, pUseCount(rep ? U2_NEW_T(unsigned int, MEMCATEGORY_GENERAL)(1) : 0)
		, useFreeMethod(inFreeMethod)
	{
        U2_SET_AUTO_SHARED_MUTEX_NULL
		if (rep)
		{
			U2_NEW_AUTO_SHARED_MUTEX
		}
	}
	U2SharedPtr(const U2SharedPtr& r)
        : pRep(0), pUseCount(0), useFreeMethod(SPFM_DELETE)
	{
		// lock & copy other mutex pointer
        
        U2_SET_AUTO_SHARED_MUTEX_NULL
        U2_MUTEX_CONDITIONAL(r.U2_AUTO_MUTEX_NAME)
        {
		    U2_LOCK_MUTEX(*r.U2_AUTO_MUTEX_NAME)
		    U2_COPY_AUTO_SHARED_MUTEX(r.U2_AUTO_MUTEX_NAME)
		    pRep = r.pRep;
		    pUseCount = r.pUseCount; 
			useFreeMethod = r.useFreeMethod;
		    // Handle zero pointer gracefully to manage STL containers
		    if(pUseCount)
		    {
			    ++(*pUseCount); 
		    }
        }
	}
	U2SharedPtr& operator=(const U2SharedPtr& r) {
		if (pRep == r.pRep)
			return *this;
		// Swap current data into a local copy
		// this ensures we deal with rhs and this being dependent
		U2SharedPtr<T> tmp(r);
		swap(tmp);
		return *this;
	}
	
	template< class Y>
	U2SharedPtr(const U2SharedPtr<Y>& r)
        : pRep(0), pUseCount(0), useFreeMethod(SPFM_DELETE)
	{
		// lock & copy other mutex pointer

        U2_SET_AUTO_SHARED_MUTEX_NULL
        U2_MUTEX_CONDITIONAL(r.U2_AUTO_MUTEX_NAME)
        {
		    U2_LOCK_MUTEX(*r.U2_AUTO_MUTEX_NAME)
		    U2_COPY_AUTO_SHARED_MUTEX(r.U2_AUTO_MUTEX_NAME)
		    pRep = r.getPointer();
		    pUseCount = r.useCountPointer();
			useFreeMethod = r.freeMethod();
		    // Handle zero pointer gracefully to manage STL containers
		    if(pUseCount)
		    {
			    ++(*pUseCount);
		    }
        }
	}
	template< class Y>
	U2SharedPtr& operator=(const U2SharedPtr<Y>& r) {
		if (pRep == r.getPointer())
			return *this;
		// Swap current data into a local copy
		// this ensures we deal with rhs and this being dependent
		U2SharedPtr<T> tmp(r);
		swap(tmp);
		return *this;
	}
	virtual ~U2SharedPtr() {
        release();
	}


	inline T& operator*() const { assert(pRep); return *pRep; }
	inline T* operator->() const { assert(pRep); return pRep; }
	inline T* get() const { return pRep; }

	/** Binds rep to the U2SharedPtr.
		@remarks
			Assumes that the U2SharedPtr is uninitialised!
	*/
	void bind(T* rep, SharedPtrFreeMethod inFreeMethod = SPFM_DELETE) {
		assert(!pRep && !pUseCount);
        U2_NEW_AUTO_SHARED_MUTEX
		U2_LOCK_AUTO_SHARED_MUTEX
		pUseCount = U2_NEW_T(unsigned int, MEMCATEGORY_GENERAL)(1);
		pRep = rep;
		useFreeMethod = inFreeMethod;
	}

	inline bool unique() const { U2_LOCK_AUTO_SHARED_MUTEX assert(pUseCount); return *pUseCount == 1; }
	inline unsigned int useCount() const { U2_LOCK_AUTO_SHARED_MUTEX assert(pUseCount); return *pUseCount; }
	inline unsigned int* useCountPointer() const { return pUseCount; }

	inline T* getPointer() const { return pRep; }
	inline SharedPtrFreeMethod freeMethod() const { return useFreeMethod; }

	inline bool isNull(void) const { return pRep == 0; }

    inline void setNull(void) { 
		if (pRep)
		{
			// can't scope lock mutex before release in case deleted
			release();
			pRep = 0;
			pUseCount = 0;
		}
    }

protected:

    inline void release(void)
    {
		bool destroyThis = false;

        /* If the mutex is not initialized to a non-zero value, then
           neither is pUseCount nor pRep.
         */

        U2_MUTEX_CONDITIONAL(U2_AUTO_MUTEX_NAME)
		{
			// lock own mutex in limited scope (must unlock before destroy)
			U2_LOCK_AUTO_SHARED_MUTEX
			if (pUseCount)
			{
				if (--(*pUseCount) == 0) 
				{
					destroyThis = true;
                }
			}
        }
		if (destroyThis)
			destroy();

        U2_SET_AUTO_SHARED_MUTEX_NULL
    }

    virtual void destroy(void)
    {
        // IF YOU GET A CRASH HERE, YOU FORGOT TO FREE UP POINTERS
        // BEFORE SHUTTING OGRE DOWN
        // Use setNull() before shutdown or make sure your pointer goes
        // out of scope before OGRE shuts down to avoid this.
		switch(useFreeMethod)
		{
		case SPFM_DELETE:
			U2_DELETE pRep;
			break;
		case SPFM_DELETE_T:
			U2_DELETE_T(pRep, T, MEMCATEGORY_GENERAL);
			break;
		case SPFM_FREE:
			U2_FREE(pRep, MEMCATEGORY_GENERAL);
			break;
		};
		// use U2_FREE instead of U2_DELETE_T since 'unsigned int' isn't a destructor
		// we only used U2_NEW_T to be able to use constructor
        U2_FREE(pUseCount, MEMCATEGORY_GENERAL);
		U2_DELETE_AUTO_SHARED_MUTEX
    }

	virtual void swap(U2SharedPtr<T> &other) 
	{
		std::swap(pRep, other.pRep);
		std::swap(pUseCount, other.pUseCount);
		std::swap(useFreeMethod, other.useFreeMethod);
#if U2_THREAD_SUPPORT
		std::swap(U2_AUTO_MUTEX_NAME, other.U2_AUTO_MUTEX_NAME);
#endif
	}
};

template<class T, class U> inline bool operator==(U2SharedPtr<T> const& a, U2SharedPtr<U> const& b)
{
	return a.get() == b.get();
}

template<class T, class U> inline bool operator!=(U2SharedPtr<T> const& a, U2SharedPtr<U> const& b)
{
	return a.get() != b.get();
}

template<class T, class U> inline bool operator<(U2SharedPtr<T> const& a, U2SharedPtr<U> const& b)
{
	return std::less<const void*>()(a.get(), b.get());
}


U2EG_NAMESPACE_END

#endif